﻿using System.Linq;

using Microsoft.WowAddonStudio.Lua.Adapters;
using Microsoft.WowAddonStudio.Lua.AST;
using Microsoft.WowAddonStudio.Lua.Model;

using NUnit.Framework;

namespace Microsoft.WowAddonStudio.Lua.Tests.Adapters
{
    [TestFixture]
    public class DocumentationAdapterTest
    {
        #region Setup/Teardown

        [SetUp]
        public void SetUp()
        {
            globalScope = new DocumentationScope();
            parserScope = new DocumentationScope(globalScope);
        }

        #endregion

        private DocumentationScope globalScope;
        private DocumentationScope parserScope;

        [Test]
        public void AssignmentTest()
        {
            var assignment = new Assignment(null)
                             {
                                 Scope = parserScope,
                                 VariableList = new Identifier(null)
                                                {
                                                    Name = "x"
                                                },
                                 ExpressionList = new Literal(null)
                                                  {
                                                      Type = LuaType.Number,
                                                      Value = "5"
                                                  }
                             };
            // Update the documentation
            DocumentationAdapter<Node>.AddDocumentation(assignment);

            Assert.AreEqual(1, globalScope.Declarations.Count());
            Assert.AreEqual(1, parserScope.Declarations.Count());
            Assert.IsNotNull(globalScope.Resolve<Declaration>("x"));
            Assert.IsNotNull(parserScope.Resolve<Declaration>("x"));
        }

        [Test]
        public void LocalAssignmentTest()
        {
            var localAssignment = new Assignment(null)
                                  {
                                      IsLocal = true,
                                      Scope = parserScope,
                                      VariableList = new Identifier(null)
                                                     {
                                                         Name = "x"
                                                     },
                                      ExpressionList = new Literal(null)
                                                       {
                                                           Type = LuaType.Number,
                                                           Value = "5"
                                                       }
                                  };

            // Update the documentation
            DocumentationAdapter<Node>.AddDocumentation(localAssignment);

            Assert.AreEqual(0, globalScope.Declarations.Count());
            Assert.AreEqual(1, parserScope.Declarations.Count());
            Assert.IsNull(globalScope.Resolve<Declaration>("x"));
            Assert.IsNotNull(parserScope.Resolve<Declaration>("x"));
        }

        [Test]
        public void ChunkMultipleScopeTest()
        {
            var functionScope = new DocumentationScope(parserScope);

            var chunk = new Chunk(null)
                        {
                            Scope = parserScope,
                            Block = new Block(null)
                                    {
                                        Scope = parserScope,
                                        StatementList = new FunctionDeclaration(null)
                                                        {
                                                            Scope = parserScope,
                                                            FunctionName = "foo",
                                                            ParameterList = new ParameterList(null)
                                                                            {
                                                                                Scope = parserScope,
                                                                                IdentifierList = new Identifier(null)
                                                                                                 {
                                                                                                     Scope = parserScope,
                                                                                                     Name = "x"
                                                                                                 }
                                                                            },
                                                            Body = new Block(null)
                                                                   {
                                                                       Scope = functionScope,
                                                                       StatementList = new Assignment(null)
                                                                                       {
                                                                                           IsLocal = true,
                                                                                           Scope = functionScope,
                                                                                           VariableList = new Identifier(null)
                                                                                                          {
                                                                                                              Scope = functionScope,
                                                                                                              Name = "y"
                                                                                                          },
                                                                                           ExpressionList = new Literal(null)
                                                                                                            {
                                                                                                                Scope = functionScope,
                                                                                                                Type = LuaType.Number,
                                                                                                                Value = "5"
                                                                                                            }
                                                                                       } 
                                                                   }
                                                        }
                                    }
                        };

            DocumentationAdapter<Node>.AddDocumentation(chunk);

            Assert.AreEqual(1, globalScope.Declarations.Count());
            Assert.IsNull(globalScope.Resolve<Declaration>("x"));
            Assert.IsNull(globalScope.Resolve<Declaration>("y"));
            Assert.IsNotNull(globalScope.Resolve<Declaration>("foo"));

            Assert.AreEqual(3, functionScope.Declarations.Count());
            Assert.IsNotNull(functionScope.Resolve<Declaration>("x"));
            Assert.IsNotNull(functionScope.Resolve<Declaration>("y"));
            Assert.IsNotNull(functionScope.Resolve<Declaration>("foo"));
            
        }

        [Test]
        public void FunctionDeclarationTest()
        {
            var functionScope = new DocumentationScope(parserScope);

            var functionDeclaration = new FunctionDeclaration(null)
                                      {
                                          Scope = parserScope,
                                          FunctionName = "foo",
                                          ParameterList = new ParameterList(null)
                                                          {
                                                              Scope = parserScope,
                                                              IdentifierList = new Identifier(null)
                                                                               {
                                                                                   Scope = parserScope,
                                                                                   Name = "x"
                                                                               }
                                                          },
                                          Body = new Block(null)
                                                 {
                                                     Scope = functionScope
                                                 }
                                      };

            DocumentationAdapter<Node>.AddDocumentation(functionDeclaration);

            Assert.AreEqual(1, globalScope.Declarations.Count());
            Assert.IsNull(globalScope.Resolve<Declaration>("x"));
            Assert.IsNotNull(globalScope.Resolve<Declaration>("foo"));

            Assert.AreEqual(2, functionScope.Declarations.Count());
            Assert.IsNotNull(functionScope.Resolve<Declaration>("x"));
            Assert.IsNotNull(functionScope.Resolve<Declaration>("foo"));
        }

        [Test]
        public void NumericForLoopTest()
        {
            var forLoopScope = new DocumentationScope(parserScope);

            var forLoop = new ForLoop(null)
                          {
                              Scope = parserScope,
                              Identifier = new Identifier(null)
                                           {
                                               Name = "i"
                                           },
                              Block = new Block(null)
                                      {
                                          Scope = forLoopScope
                                      }

                          };

            DocumentationAdapter<Node>.AddDocumentation(forLoop);

            Assert.AreEqual(1, forLoopScope.Declarations.Count());
            Assert.IsNotNull(forLoopScope.Resolve<Declaration>("i"));
        }

        [Test]
        public void GenericForLoopTest()
        {
            var forLoopScope = new DocumentationScope(parserScope);

            var forLoop = new ForLoop(null)
                          {
                              Scope = parserScope,
                              IdentifierList = Node.LinkedList(new Identifier(null)
                                                               {
                                                                   Name = "i"
                                                               },
                                                               new Identifier(null)
                                                               {
                                                                   Name = "j"
                                                               },
                                                               new Identifier(null)
                                                               {
                                                                   Name = "k"
                                                               }),
                              Block = new Block(null)
                                      {
                                          Scope = forLoopScope
                                      }
                          };

            DocumentationAdapter<Node>.AddDocumentation(forLoop);

            Assert.AreEqual(3, forLoopScope.Declarations.Count());
            Assert.IsNotNull(forLoopScope.Resolve<Declaration>("i"));
            Assert.IsNotNull(forLoopScope.Resolve<Declaration>("j"));
            Assert.IsNotNull(forLoopScope.Resolve<Declaration>("k"));
        }

        [Test]
        public void LocalDeclarationTest()
        {
            // local foo, bar;
            var localDeclaration = new LocalDeclaration(null)
                                   {
                                       Scope = parserScope,
                                       IdentifierList = Node.LinkedList(new Identifier(null)
                                                                        {
                                                                            Name = "foo"
                                                                        },
                                                                        new Identifier(null)
                                                                        {
                                                                            Name = "bar"
                                                                        })
                                   };

            DocumentationAdapter<Node>.AddDocumentation(localDeclaration);

            Assert.AreEqual(0, globalScope.Declarations.Count());
            Assert.AreEqual(2, parserScope.Declarations.Count());
            Assert.IsNotNull(parserScope.Resolve<Declaration>("foo"));
            Assert.IsNotNull(parserScope.Resolve<Declaration>("bar"));
        }

        [Test]
        public void TableAssignmentTest()
        {
            // foo = { bar = 5 }
            var tableAssignment = new Assignment(null)
                                  {
                                      Scope = parserScope,
                                      VariableList = new Identifier(null)
                                                     {
                                                         Name = "foo"
                                                     },
                                      ExpressionList = new TableConstructor(null)
                                                       {
                                                           Scope = parserScope,
                                                           FieldList = new Field(null)
                                                                       {
                                                                           Identifier = new Identifier(null)
                                                                                        {
                                                                                            Name = "bar"
                                                                                        },
                                                                           Expression = new Literal(null)
                                                                                        {
                                                                                            Type = LuaType.Number,
                                                                                            Value = "5"
                                                                                        }
                                                                       }
                                                       }
                                  };

            DocumentationAdapter<Node>.AddDocumentation(tableAssignment);

            Assert.AreEqual(1, globalScope.Declarations.Count());
            Assert.IsNotNull(globalScope.Resolve<Declaration>("foo.bar"));
        }

        [Test]
        public void QualifiedAssignmentTest()
        {
            // foo = {}; foo.bar = 5
            var statements = new Block(null)
                             {
                                 Scope = parserScope,
                                 StatementList = Node.LinkedList(new Assignment(null)
                                                                 {
                                                                     Scope = parserScope,
                                                                     VariableList = new Identifier(null)
                                                                                    {
                                                                                        Name = "foo"
                                                                                    },
                                                                     ExpressionList = new TableConstructor(null)
                                                                                      {
                                                                                          Scope = parserScope
                                                                                      }
                                                                 }, new Assignment(null)
                                                                    {
                                                                        Scope = parserScope,
                                                                        VariableList = (QualifiedName)"foo.bar",
                                                                        ExpressionList = new Literal(null)
                                                                                         {
                                                                                             Type = LuaType.Number,
                                                                                             Value = "5"
                                                                                         }
                                                                    })
                             };

            DocumentationAdapter<Node>.AddDocumentation(statements);

            Assert.AreEqual(1, globalScope.Declarations.Count());
            Assert.IsNotNull(globalScope.Resolve<Declaration>("foo.bar"));
        }

        [Test]
        public void QualifiedFunctionDeclarationTest()
        {
            var statements = new Block(null)
                             {
                                 Scope = parserScope,
                                 StatementList = Node.LinkedList<Statement>(new Assignment(null)
                                                                            {
                                                                                Scope = parserScope,
                                                                                VariableList = new Identifier(null)
                                                                                               {
                                                                                                   Name = "foo"
                                                                                               },
                                                                                ExpressionList = new TableConstructor(null)
                                                                                                 {
                                                                                                     Scope = parserScope
                                                                                                 }
                                                                            }, new FunctionDeclaration(null)
                                                                               {
                                                                                   Scope = parserScope,
                                                                                   FunctionName = "foo:bar"
                                                                               })
                             };

            DocumentationAdapter<Node>.AddDocumentation(statements);

            Assert.AreEqual(1, globalScope.Declarations.Count());
            Assert.IsNotNull(globalScope.Resolve<Declaration>("foo.bar"));
            Assert.IsNotNull(globalScope.Resolve<Declaration>("foo:bar"));
        }
    }
}